# -*- coding: utf-8 -*-
"""
交易图表生成模块
"""

import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import numpy as np
import pandas as pd
from datetime import datetime, timedelta
import warnings
from typing import List, Dict, Optional, Tuple
from matplotlib.patches import Rectangle
import matplotlib.patches as mpatches
import mplfinance as mpf
import logging

logger = logging.getLogger(__name__)

# 设置中文字体和样式
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['figure.facecolor'] = 'white'
plt.rcParams['axes.facecolor'] = 'white'
plt.rcParams['axes.grid'] = True
plt.rcParams['grid.alpha'] = 0.3

class ProfessionalChartGenerator:
    """统一的交易图表生成器 - 包含汇总分析和个股K线图"""
    
    def __init__(self, trades: List[Dict], initial_capital: float = 100000, 
                 daily_data: Optional[Dict[str, pd.DataFrame]] = None,
                 xtdata_module = None,
                 warmup_start_date = None,
                 daily_capital_usage: List[Dict] = None):
        """
        初始化图表生成器
        
        Args:
            trades: 交易记录列表
            initial_capital: 初始资金
            daily_data: 日线数据字典 {stock_code: DataFrame}，用于生成K线图
            xtdata_module: xtdata模块，用于获取股票名称
            warmup_start_date: 预热期结束日期（预测开始日期），K线图从此日期开始显示
            daily_capital_usage: 每日资金占用记录 [{date: str, capital_used: float}]
        """
        self.trades = trades
        self.initial_capital = initial_capital
        self.daily_data = daily_data or {}
        self.xtdata_module = xtdata_module
        self.warmup_start_date = warmup_start_date
        
        # 创建统一的交易分析器（一次计算，到处使用）
        from utils.trade_analyzer import TradeAnalyzer
        # 计算当前资金
        current_capital = initial_capital + sum(t.get('profit', 0) for t in trades if t.get('direction') == 'sell' or t.get('action') == 'sell')
        self.analyzer = TradeAnalyzer(trades, initial_capital, current_capital, daily_capital_usage)
        self.stats = self.analyzer.get_statistics(exclude_forced_close=True)
        
        self.colors = {
            'primary': '#2E86AB',      # 主色调 - 深蓝
            'secondary': '#A23B72',    # 次色调 - 深红
            'success': '#F18F01',      # 成功色 - 橙色
            'danger': '#C73E1D',       # 危险色 - 红色
            'buy': '#00C851',          # 买入色 - 绿色
            'sell': '#FF4444',         # 卖出色 - 红色
            'profit': '#FF4444',       # 盈利色 - 红色（中国股市习惯）
            'loss': '#00C851',         # 亏损色 - 绿色（中国股市习惯）
            'neutral': '#6C757D',      # 中性色 - 灰色
            'background': '#F8F9FA',   # 背景色 - 浅灰
            'grid': '#E9ECEF'          # 网格色 - 浅灰
        }
        
    def generate_all_charts(self, save_path: str = "professional_analysis"):
        """
        生成全套交易图表（汇总分析 + 个股K线）
        
        Args:
            save_path: 保存路径前缀
        """
        # 1. 核心绩效图表
        # self._plot_equity_curve(f"{save_path}_资产曲线.png")
        
        # 2. 风险收益分析
        # self._plot_risk_return_analysis(f"{save_path}_风险收益分析.png")
        
        # 3. 交易分析图表（保留）
        self._plot_trading_analysis(f"{save_path}_交易分析.png")
        
        # 4. 月度收益热力图
        # self._plot_monthly_heatmap(f"{save_path}_月度收益热力图.png")
        
        # 5. 回撤分析图
        # self._plot_drawdown_analysis(f"{save_path}_回撤分析.png")
        
        # 6. 风险指标雷达图
        # self._plot_risk_radar(f"{save_path}_风险雷达图.png")
        
        # 7. 个股K线图
        self._generate_kline_charts(save_path)
        
    def _plot_equity_curve(self, filename: str):
        """绘制资产曲线图 - 专业版"""
        if not self.trades:
            return
            
        fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(15, 10), 
                                     gridspec_kw={'height_ratios': [3, 1]})
        
        # 准备数据 - 使用现金余额（因为持仓市值计算复杂，需要实时价格数据）
        times = [self._parse_time(trade.get('time', trade.get('date', ''))) for trade in self.trades]
        capitals = [trade.get('capital', trade.get('capital_after', self.initial_capital)) for trade in self.trades]
        
        # 计算收益率
        returns = [(cap - self.initial_capital) / self.initial_capital * 100 
                  for cap in capitals]
        
        # 主图：资产曲线（现金余额）
        ax1.plot(times, capitals, color=self.colors['primary'], 
                linewidth=3, label='现金余额', alpha=0.8)
        ax1.axhline(y=self.initial_capital, color=self.colors['neutral'], 
                   linestyle='--', alpha=0.7, label='初始资金')
        
        # 交易点标注
        buy_times = [self._parse_time(trade.get('time', trade.get('date', ''))) for trade in self.trades 
                    if trade.get('direction', trade.get('action', '')) == 'buy']
        buy_capitals = [trade.get('capital', trade.get('capital_after', self.initial_capital)) for trade in self.trades 
                       if trade.get('direction', trade.get('action', '')) == 'buy']
        sell_times = [self._parse_time(trade.get('time', trade.get('date', ''))) for trade in self.trades 
                    if trade.get('direction', trade.get('action', '')) == 'sell']
        sell_capitals = [trade.get('capital', trade.get('capital_after', self.initial_capital)) for trade in self.trades 
                        if trade.get('direction', trade.get('action', '')) == 'sell']
        
        ax1.scatter(buy_times, buy_capitals, color=self.colors['buy'], 
                   marker='^', s=120, label='买入', zorder=5, edgecolors='white', linewidth=2)
        ax1.scatter(sell_times, sell_capitals, color=self.colors['sell'], 
                   marker='v', s=120, label='卖出', zorder=5, edgecolors='white', linewidth=2)
        
        # 填充盈利和亏损区域
        ax1.fill_between(times, self.initial_capital, capitals, 
                        where=[cap >= self.initial_capital for cap in capitals],
                        color=self.colors['profit'], alpha=0.2, label='盈利区域')
        ax1.fill_between(times, self.initial_capital, capitals, 
                        where=[cap < self.initial_capital for cap in capitals],
                        color=self.colors['loss'], alpha=0.2, label='亏损区域')
        
        # 子图：收益率
        ax2.plot(times, returns, color=self.colors['secondary'], 
                linewidth=2, label='累计收益率')
        ax2.axhline(y=0, color=self.colors['neutral'], linestyle='-', alpha=0.5)
        ax2.fill_between(times, 0, returns, 
                        where=[r >= 0 for r in returns],
                        color=self.colors['profit'], alpha=0.3)
        ax2.fill_between(times, 0, returns, 
                        where=[r < 0 for r in returns],
                        color=self.colors['loss'], alpha=0.3)
        
        # 设置样式
        ax1.set_title('资产曲线分析（现金余额）', fontsize=16, fontweight='bold', pad=20)
        ax1.set_ylabel('资产价值 (元)', fontsize=12)
        
        # 只有当有图例项时才显示图例
        handles, labels = ax1.get_legend_handles_labels()
        if handles:
            ax1.legend(loc='upper left', frameon=True, fancybox=True, shadow=True)
        
        ax1.grid(True, alpha=0.3, color=self.colors['grid'])
        
        ax2.set_title('累计收益率', fontsize=14, fontweight='bold')
        ax2.set_ylabel('收益率 (%)', fontsize=12)
        ax2.set_xlabel('时间', fontsize=12)
        ax2.grid(True, alpha=0.3, color=self.colors['grid'])
        
        # 格式化x轴
        for ax in [ax1, ax2]:
            ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))
            ax.xaxis.set_major_locator(mdates.MonthLocator(interval=1))
            plt.setp(ax.xaxis.get_majorticklabels(), rotation=45)
        
        plt.tight_layout()
        plt.savefig(filename, dpi=300, bbox_inches='tight', 
                   facecolor='white', edgecolor='none')
        plt.close()
        
    def _plot_risk_return_analysis(self, filename: str):
        """绘制风险收益分析图"""
        if not self.trades:
            return
            
        fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(16, 12))
        
        # 计算基础指标
        capitals = [trade.get('capital', trade.get('capital_after', self.initial_capital)) for trade in self.trades]
        returns = [(cap - self.initial_capital) / self.initial_capital * 100 
                  for cap in capitals]
        
        # 1. 滚动收益率
        # 过滤有效的时间数据
        valid_trades = [trade for trade in self.trades if trade.get('time') or trade.get('date')]
        times = [self._parse_time(trade.get('time', trade.get('date', ''))) for trade in valid_trades]
        valid_returns = [(trade.get('capital', trade.get('capital_after', self.initial_capital)) - self.initial_capital) / self.initial_capital * 100 for trade in valid_trades]
        
        if len(valid_returns) > 30:
            window = min(30, len(valid_returns) // 4)
            rolling_returns = pd.Series(valid_returns).rolling(window=window).mean()
            
            ax1.plot(times, rolling_returns, color=self.colors['primary'], 
                    linewidth=2, label=f'{window}日滚动收益率')
            ax1.axhline(y=0, color=self.colors['neutral'], linestyle='--', alpha=0.7)
            ax1.fill_between(times, 0, rolling_returns, 
                           where=[r >= 0 for r in rolling_returns],
                           color=self.colors['profit'], alpha=0.3)
            ax1.fill_between(times, 0, rolling_returns, 
                           where=[r < 0 for r in rolling_returns],
                           color=self.colors['loss'], alpha=0.3)
        
        ax1.set_title('滚动收益率分析', fontsize=14, fontweight='bold')
        ax1.set_ylabel('收益率 (%)')
        ax1.legend()
        ax1.grid(True, alpha=0.3)
        
        # 2. 收益率分布
        ax2.hist(returns, bins=20, color=self.colors['primary'], 
                alpha=0.7, edgecolor='white', linewidth=1)
        ax2.axvline(x=np.mean(returns), color=self.colors['danger'], 
                   linestyle='--', linewidth=2, label=f'均值: {np.mean(returns):.2f}%')
        ax2.axvline(x=np.median(returns), color=self.colors['success'], 
                   linestyle='--', linewidth=2, label=f'中位数: {np.median(returns):.2f}%')
        
        ax2.set_title('收益率分布', fontsize=14, fontweight='bold')
        ax2.set_xlabel('收益率 (%)')
        ax2.set_ylabel('频次')
        ax2.legend()
        ax2.grid(True, alpha=0.3)
        
        # 3. 波动率分析
        if len(returns) > 1:
            volatility = np.std(returns)
            ax3.bar(['策略波动率'], [volatility], color=self.colors['secondary'], 
                   alpha=0.7, width=0.5)
            ax3.set_title('策略波动率', fontsize=14, fontweight='bold')
            ax3.set_ylabel('波动率 (%)')
            ax3.grid(True, alpha=0.3, axis='y')
        
        # 4. 夏普比率
        if len(returns) > 1:
            sharpe_ratio = np.mean(returns) / np.std(returns) if np.std(returns) > 0 else 0
            colors = [self.colors['profit'] if sharpe_ratio > 1 else 
                     self.colors['success'] if sharpe_ratio > 0.5 else 
                     self.colors['loss']]
            
            ax4.bar(['夏普比率'], [sharpe_ratio], color=colors, alpha=0.7, width=0.5)
            ax4.axhline(y=1, color=self.colors['neutral'], linestyle='--', 
                       alpha=0.7, label='优秀线 (1.0)')
            ax4.axhline(y=0.5, color=self.colors['neutral'], linestyle=':', 
                       alpha=0.7, label='及格线 (0.5)')
            
            ax4.set_title('夏普比率', fontsize=14, fontweight='bold')
            ax4.set_ylabel('夏普比率')
            ax4.legend()
            ax4.grid(True, alpha=0.3, axis='y')
        
        plt.tight_layout()
        plt.savefig(filename, dpi=300, bbox_inches='tight', 
                   facecolor='white', edgecolor='none')
        plt.close()
        
    def _plot_trading_analysis(self, filename: str):
        """绘制交易分析图"""
        if not self.trades:
            return
            
        fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(16, 12))
        
        # 1. 持有期分析（使用统一的TradeAnalyzer结果）
        holding_periods = self.stats['holding_periods']
        
        if holding_periods:
            ax1.hist(holding_periods, bins=10, color=self.colors['primary'], 
                    alpha=0.7, edgecolor='white', linewidth=1)
            ax1.axvline(x=self.stats['avg_holding_period'], color=self.colors['danger'], 
                       linestyle='--', linewidth=2, 
                       label=f'平均: {self.stats["avg_holding_period"]:.1f}天')
            ax1.axvline(x=self.stats['median_holding_period'], color=self.colors['success'], 
                       linestyle='--', linewidth=2, 
                       label=f'中位数: {self.stats["median_holding_period"]:.1f}天')
        
        ax1.set_title('持有期分布', fontsize=14, fontweight='bold')
        ax1.set_xlabel('持有天数')
        ax1.set_ylabel('频次')
        ax1.legend()
        ax1.grid(True, alpha=0.3)
        
        # 2. 盈亏分析（使用统一的TradeAnalyzer结果）
        profits = self.stats['profits']
        losses = self.stats['losses']
        
        # 盈亏统计（使用统一的TradeAnalyzer结果）
        win_rate = self.stats['win_rate']
        profit_loss_ratio = self.stats['profit_loss_ratio']
        net_profit = self.stats['net_profit']
        total_profit_amount = self.stats['total_profit_amount']
        total_loss_amount = self.stats['total_loss_amount']
        
        # 盈亏分析图 - 明确区分盈利和亏损
        if profits or losses:
            # 计算盈亏金额（确保正确性）
            profit_count = len(profits)
            loss_count = len(losses)
            
            # 如果总盈亏为正，显示盈利占比；如果为负，显示亏损占比
            if total_profit_amount > 0 or total_loss_amount > 0:
                # 使用明确的顺序：[盈利, 亏损]
                sizes = [total_profit_amount, total_loss_amount]
                labels = [
                    f'总盈利: {total_profit_amount:,.0f}元\n({profit_count}次)',
                    f'总亏损: {total_loss_amount:,.0f}元\n({loss_count}次)'
                ]
                colors = [self.colors['profit'], self.colors['loss']]
                
                wedges, texts, autotexts = ax2.pie(sizes, labels=labels, colors=colors, 
                                                 autopct='%1.1f%%', startangle=90,
                                                 textprops={'fontsize': 14, 'fontweight': 'bold'})
                # 增大百分比数字的字体
                for autotext in autotexts:
                    autotext.set_fontsize(16)
                    autotext.set_fontweight('bold')
                
                # 标题显示最终状态
                status_text = f"净盈利: {net_profit:+,.0f}元" if net_profit >= 0 else f"净亏损: {net_profit:+,.0f}元"
                title_color = self.colors['profit'] if net_profit >= 0 else self.colors['loss']
                ax2.set_title(f'盈亏分析 (胜率: {win_rate:.1f}%)\n{status_text}', 
                            fontsize=13, fontweight='bold', color=title_color)
        
        # 3. 月度交易统计（剔除强制平仓）
        monthly_trades = {}
        for trade in self.trades:
            # 跳过强制平仓
            if trade.get('is_forced_close', False):
                continue
            trade_date = self._parse_time(trade.get('time', trade.get('date', '')))
            month_key = trade_date.strftime('%Y-%m')
            monthly_trades[month_key] = monthly_trades.get(month_key, 0) + 1
        
        if monthly_trades:
            months = list(monthly_trades.keys())
            counts = list(monthly_trades.values())
            
            bars = ax3.bar(months, counts, color=self.colors['primary'], alpha=0.7)
            ax3.set_title('月度交易次数', fontsize=14, fontweight='bold')
            ax3.set_xlabel('月份')
            ax3.set_ylabel('交易次数')
            ax3.tick_params(axis='x', rotation=45)
            ax3.grid(True, alpha=0.3, axis='y')
            
            # 添加数值标签
            for bar, count in zip(bars, counts):
                ax3.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.1,
                        str(count), ha='center', va='bottom', fontweight='bold')
        
        # 4. 关键指标汇总（使用统一的TradeAnalyzer结果）
        capital_usage_return = self.stats['capital_usage_return']
        capitals = [trade.get('capital', trade.get('capital_after', self.initial_capital)) for trade in self.trades]
        max_drawdown = self._calculate_max_drawdown(capitals)
        
        metrics = ['资金使用收益率', '最大回撤', '胜率', '盈亏比']
        values = [f'{capital_usage_return:.2f}%', f'{max_drawdown:.2f}%', 
                 f'{win_rate:.1f}%', f'{profit_loss_ratio:.2f}']
        colors_metrics = [self.colors['profit'] if capital_usage_return > 0 else self.colors['loss'],
                         self.colors['loss'] if max_drawdown > 10 else self.colors['success'],
                         self.colors['profit'] if win_rate > 50 else self.colors['loss'],
                         self.colors['profit'] if profit_loss_ratio > 1 else self.colors['loss']]
        
        bars = ax4.bar(metrics, [1]*4, color=colors_metrics, alpha=0.7)
        ax4.set_title('关键指标汇总', fontsize=14, fontweight='bold')
        ax4.set_ylim(0, 1.2)
        
        # 添加数值标签
        for bar, value in zip(bars, values):
            ax4.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.05,
                    value, ha='center', va='bottom', fontweight='bold', fontsize=12)
        
        ax4.set_yticks([])
        ax4.set_xticklabels(metrics, rotation=45, ha='right', fontsize=12)
        
        plt.tight_layout()
        plt.savefig(filename, dpi=300, bbox_inches='tight', 
                   facecolor='white', edgecolor='none')
        plt.close()
        
    def _plot_monthly_heatmap(self, filename: str):
        """绘制月度收益热力图"""
        if not self.trades:
            return
            
        # 计算月度收益
        monthly_returns = {}
        capitals = [trade.get('capital', trade.get('capital_after', self.initial_capital)) for trade in self.trades]
        times = [self._parse_time(trade.get('time', trade.get('date', ''))) for trade in self.trades]
        
        # 按月份分组计算收益
        for i, (time, capital) in enumerate(zip(times, capitals)):
            month_key = time.strftime('%Y-%m')
            if month_key not in monthly_returns:
                monthly_returns[month_key] = []
            monthly_returns[month_key].append(capital)
        
        # 计算每月最终收益
        monthly_final_returns = {}
        prev_capital = self.initial_capital
        for month in sorted(monthly_returns.keys()):
            month_capitals = monthly_returns[month]
            final_capital = month_capitals[-1]
            monthly_return = (final_capital - prev_capital) / prev_capital * 100
            monthly_final_returns[month] = monthly_return
            prev_capital = final_capital
        
        if not monthly_final_returns:
            return
            
        # 创建热力图数据
        months = sorted(monthly_final_returns.keys())
        years = sorted(set([m.split('-')[0] for m in months]))
        
        # 创建DataFrame
        heatmap_data = []
        for year in years:
            row = []
            for month in range(1, 13):
                month_key = f"{year}-{month:02d}"
                if month_key in monthly_final_returns:
                    row.append(monthly_final_returns[month_key])
                else:
                    row.append(np.nan)
            heatmap_data.append(row)
        
        heatmap_df = pd.DataFrame(heatmap_data, 
                                 index=years, 
                                 columns=[f'{i:02d}月' for i in range(1, 13)])
        
        # 绘制热力图
        fig, ax = plt.subplots(figsize=(12, 8))
        
        # 使用matplotlib绘制热力图
        im = ax.imshow(heatmap_df.values, cmap='RdYlGn', aspect='auto', vmin=-10, vmax=10)
        
        # 设置坐标轴
        ax.set_xticks(range(len(heatmap_df.columns)))
        ax.set_yticks(range(len(heatmap_df.index)))
        ax.set_xticklabels(heatmap_df.columns)
        ax.set_yticklabels(heatmap_df.index)
        
        # 添加数值标注
        for i in range(len(heatmap_df.index)):
            for j in range(len(heatmap_df.columns)):
                value = heatmap_df.iloc[i, j]
                if not pd.isna(value):
                    text_color = 'white' if abs(value) > 5 else 'black'
                    ax.text(j, i, f'{value:.1f}', ha='center', va='center', 
                           color=text_color, fontweight='bold')
        
        # 添加颜色条
        cbar = plt.colorbar(im, ax=ax)
        cbar.set_label('月度收益率 (%)', fontsize=12)
        
        ax.set_title('月度收益热力图', fontsize=16, fontweight='bold', pad=20)
        ax.set_xlabel('月份', fontsize=12)
        ax.set_ylabel('年份', fontsize=12)
        
        plt.tight_layout()
        plt.savefig(filename, dpi=300, bbox_inches='tight', 
                   facecolor='white', edgecolor='none')
        plt.close()
        
    def _plot_drawdown_analysis(self, filename: str):
        """绘制回撤分析图"""
        if not self.trades:
            return
            
        capitals = [trade.get('capital', trade.get('capital_after', self.initial_capital)) for trade in self.trades]
        times = [self._parse_time(trade.get('time', trade.get('date', ''))) for trade in self.trades]
        
        # 计算回撤
        peak = capitals[0]
        drawdowns = []
        drawdown_periods = []
        
        for i, capital in enumerate(capitals):
            if capital > peak:
                peak = capital
            drawdown = (peak - capital) / peak * 100
            drawdowns.append(drawdown)
            
            # 记录回撤期间
            if drawdown > 0:
                if not drawdown_periods or drawdown_periods[-1][1] != i-1:
                    drawdown_periods.append([i, i])
                else:
                    drawdown_periods[-1][1] = i
        
        fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(15, 10))
        
        # 1. 回撤曲线
        ax1.fill_between(times, 0, drawdowns, color=self.colors['loss'], 
                        alpha=0.3, label='回撤区域')
        ax1.plot(times, drawdowns, color=self.colors['danger'], 
                linewidth=2, label='回撤曲线')
        
        max_drawdown = max(drawdowns)
        max_dd_idx = drawdowns.index(max_drawdown)
        ax1.scatter([times[max_dd_idx]], [max_drawdown], 
                   color=self.colors['danger'], s=100, zorder=5,
                   label=f'最大回撤: {max_drawdown:.2f}%')
        
        ax1.set_title('回撤分析', fontsize=16, fontweight='bold', pad=20)
        ax1.set_ylabel('回撤 (%)')
        ax1.legend()
        ax1.grid(True, alpha=0.3)
        
        # 2. 资产曲线与回撤对比
        ax2.plot(times, capitals, color=self.colors['primary'], 
                linewidth=2, label='资产曲线')
        
        # 标记回撤期间
        for period in drawdown_periods:
            start_idx, end_idx = period
            ax2.axvspan(times[start_idx], times[end_idx], 
                       color=self.colors['loss'], alpha=0.2)
        
        ax2.set_title('资产曲线与回撤期间', fontsize=14, fontweight='bold')
        ax2.set_ylabel('资产价值 (元)')
        ax2.set_xlabel('时间')
        ax2.legend()
        ax2.grid(True, alpha=0.3)
        
        # 格式化x轴
        for ax in [ax1, ax2]:
            ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))
            ax.xaxis.set_major_locator(mdates.MonthLocator(interval=1))
            plt.setp(ax.xaxis.get_majorticklabels(), rotation=45)
        
        plt.tight_layout()
        plt.savefig(filename, dpi=300, bbox_inches='tight', 
                   facecolor='white', edgecolor='none')
        plt.close()
        
    def _plot_risk_radar(self, filename: str):
        """绘制风险指标雷达图"""
        if not self.trades:
            return
            
        # 计算各项指标
        capitals = [trade.get('capital', trade.get('capital_after', self.initial_capital)) for trade in self.trades]
        returns = [(cap - self.initial_capital) / self.initial_capital * 100 
                  for cap in capitals]
        
        # 基础指标
        capital_usage_return = self.stats.get('capital_usage_return', 0)
        volatility = np.std(returns) if len(returns) > 1 else 0
        sharpe_ratio = capital_usage_return / volatility if volatility > 0 else 0
        max_drawdown = self._calculate_max_drawdown(capitals)
        
        # 交易指标（使用统一的TradeAnalyzer结果）
        win_rate = self.stats['win_rate']
        profit_loss_ratio = self.stats['profit_loss_ratio']
        
        # 标准化指标 (0-100分)
        metrics = ['资金使用收益率', '夏普比率', '最大回撤', '胜率', '盈亏比', '稳定性']
        
        # 标准化函数
        def normalize_score(value, min_val, max_val, reverse=False):
            if reverse:  # 回撤越小越好
                score = max(0, min(100, (max_val - value) / (max_val - min_val) * 100))
            else:
                score = max(0, min(100, (value - min_val) / (max_val - min_val) * 100))
            return score
        
        scores = [
            normalize_score(capital_usage_return, -20, 50),  # 资金使用收益率
            normalize_score(sharpe_ratio, -1, 3),    # 夏普比率
            normalize_score(max_drawdown, 0, 30, reverse=True),  # 最大回撤(反向)
            normalize_score(win_rate, 0, 100),      # 胜率
            normalize_score(profit_loss_ratio, 0, 3), # 盈亏比
            normalize_score(100-volatility, 0, 100)  # 稳定性(波动率反向)
        ]
        
        # 绘制雷达图
        fig, ax = plt.subplots(figsize=(10, 10), subplot_kw=dict(projection='polar'))
        
        # 角度
        angles = np.linspace(0, 2 * np.pi, len(metrics), endpoint=False).tolist()
        scores += scores[:1]  # 闭合图形
        angles += angles[:1]
        
        # 绘制雷达图
        ax.plot(angles, scores, 'o-', linewidth=2, color=self.colors['primary'])
        ax.fill(angles, scores, alpha=0.25, color=self.colors['primary'])
        
        # 设置标签
        ax.set_xticks(angles[:-1])
        ax.set_xticklabels(metrics, fontsize=12)
        ax.set_ylim(0, 100)
        ax.set_yticks([20, 40, 60, 80, 100])
        ax.set_yticklabels(['20', '40', '60', '80', '100'], fontsize=10)
        ax.grid(True)
        
        # 添加分数标签
        for angle, score, metric in zip(angles[:-1], scores[:-1], metrics):
            ax.text(angle, score + 5, f'{score:.0f}', 
                   ha='center', va='center', fontweight='bold', fontsize=10)
        
        # 添加实际数值
        actual_values = [f'{capital_usage_return:.1f}%', f'{sharpe_ratio:.2f}', 
                        f'{max_drawdown:.1f}%', f'{win_rate:.0f}%', 
                        f'{profit_loss_ratio:.2f}', f'{100-volatility:.0f}']
        
        # 在图表下方添加实际数值
        fig.text(0.5, 0.02, f"实际数值: {', '.join(actual_values)}", 
                ha='center', fontsize=10, style='italic')
        
        plt.title('策略风险指标雷达图', fontsize=16, fontweight='bold', pad=30)
        
        plt.tight_layout()
        plt.savefig(filename, dpi=300, bbox_inches='tight', 
                   facecolor='white', edgecolor='none')
        plt.close()
        
    def _parse_time(self, time_str: str) -> datetime:
        """解析时间字符串 - 简化版本"""
        if not time_str:
            return datetime.now()
        
        # 直接尝试解析，失败就返回当前时间
        try:
            if ' ' in time_str:
                return datetime.strptime(time_str.split()[0], '%Y-%m-%d')
            elif '-' in time_str:
                return datetime.strptime(time_str, '%Y-%m-%d')
            elif len(time_str) == 8 and time_str.isdigit():
                return datetime.strptime(time_str, '%Y%m%d')
            else:
                return datetime.strptime(time_str, '%Y-%m-%d')
        except:
            return datetime.now()
        
    def _calculate_max_drawdown(self, capitals: List[float]) -> float:
        """计算最大回撤"""
        if not capitals:
            return 0
            
        peak = capitals[0]
        max_dd = 0
        
        for capital in capitals:
            if capital > peak:
                peak = capital
            drawdown = (peak - capital) / peak * 100
            max_dd = max(max_dd, drawdown)
            
        return max_dd
    
    def _generate_kline_charts(self, save_path_prefix: str):
        """
        生成个股K线图（私有方法）
        
        Args:
            save_path_prefix: 保存路径前缀
        """
        if not self.trades:
            logger.warning("无交易数据可绘制")
            return
        
        # 获取所有交易过的股票
        traded_stocks = set()
        for trade in self.trades:
            traded_stocks.add(trade['stock'])
        
        if not traded_stocks:
            logger.warning("无交易过的股票")
            return
        
        # 为每只股票绘制K线图
        for stock in traded_stocks:
            try:
                self._plot_single_stock_kline(stock, save_path_prefix)
            except Exception as e:
                logger.error(f"绘制{stock}K线图时出错: {str(e)}")
    
    def _plot_single_stock_kline(self, stock: str, save_path_prefix: str):
        """
        绘制单只股票的K线图，包含盈亏信息（私有方法）
        
        功能：显示K线、成交量、均线、买卖点标记、交易统计信息框
        
        Args:
            stock: 股票代码
            save_path_prefix: 保存路径前缀
        """
        # 1. 准备数据
        stock_df = _prepare_stock_data(stock, self.daily_data, self.warmup_start_date)
        if stock_df is None:
            return
        
        # 2. 提取交易点
        stock_trades = [t for t in self.trades if t['stock'] == stock]
        buy_points, sell_points, trade_results = _extract_trade_points(stock_trades)
        
        # 3. 获取股票名称
        stock_name = _get_stock_name(stock, self.xtdata_module)
        
        # 4. 创建样式和路径
        style = _create_kline_style()
        save_path = _generate_save_path(save_path_prefix, stock, stock_name)
        
        # 5. 先用mpf绘制K线图
        fig, axes = mpf.plot(
            stock_df,
            type='candle',
            style=style,
            title='',
            ylabel='价格 (元)',
            ylabel_lower='成交量',
            volume=True,
            mav=(5, 10, 20),
            addplot=[],
            figsize=KlineConfig.FIGURE_SIZE,
            tight_layout=False,
            returnfig=True,
            xrotation=45,
            datetime_format='%Y-%m-%d',
            warn_too_much_data=10000
        )
        
        # 6. 获取主图轴对象
        main_ax = axes[0] if hasattr(axes, '__len__') and len(axes) >= 1 else axes
        
        # 7. 添加多颜色标题
        _add_multicolor_title(main_ax, stock, stock_name)
        
        # 8. 添加买卖点标记
        for point in buy_points:
            _add_trade_marker(main_ax, stock_df, point, is_buy=True)
        
        for point in sell_points:
            is_forced = point.get('is_forced_close', False)
            _add_trade_marker(main_ax, stock_df, point, is_buy=False, is_forced_close=is_forced)
        
        # 9. 调整图表边距（让K线图显示区域更大）
        fig.subplots_adjust(
            left=KlineConfig.MARGINS['left'],
            right=KlineConfig.MARGINS['right'],
            top=KlineConfig.MARGINS['top'],
            bottom=KlineConfig.MARGINS['bottom'],
            hspace=KlineConfig.MARGINS['hspace']
        )
        
        # 10. 在K线图左上角悬浮显示交易信息
        if trade_results:
            # 创建交易信息文本
            info_text = _create_trade_info_text(trade_results, buy_points, sell_points)
            
            # 在主图左上角悬浮显示
            try:
                main_ax.text(0.02, 0.98, info_text, transform=main_ax.transAxes,
                            fontsize=9, verticalalignment='top', horizontalalignment='left',
                            bbox=dict(boxstyle='round,pad=0.5', facecolor='lightblue', alpha=0.85, edgecolor='blue', linewidth=1.5),
                            fontfamily=KlineConfig.FONTS, zorder=1000)
            except Exception:
                main_ax.text(0.02, 0.98, info_text, transform=main_ax.transAxes,
                            fontsize=9, verticalalignment='top', horizontalalignment='left',
                            bbox=dict(boxstyle='round,pad=0.5', facecolor='lightblue', alpha=0.85, edgecolor='blue', linewidth=1.5),
                            zorder=1000)
        
        # 11. 保存图片
        if save_path:
            fig.savefig(save_path, dpi=KlineConfig.DPI, bbox_inches='tight',
                       facecolor='white', edgecolor='none')
            logger.info(f"{stock}K线图已保存到: {save_path}")
        
        plt.close(fig)


def generate_professional_charts(trades: List[Dict], initial_capital: float = 100000, 
                               save_path: str = "professional_analysis",
                               daily_data: Optional[Dict[str, pd.DataFrame]] = None,
                               xtdata_module = None,
                               warmup_start_date = None,
                               daily_capital_usage: List[Dict] = None):
    """
    生成交易图表的便捷函数（汇总分析 + K线图）
    
    Args:
        trades: 交易记录列表
        initial_capital: 初始资金
        save_path: 保存路径前缀
        daily_data: 日线数据字典（用于生成K线图）
        xtdata_module: xtdata模块（用于获取股票名称）
        warmup_start_date: 预热期结束日期（预测开始日期），K线图从此日期开始显示
        daily_capital_usage: 每日资金占用记录
    """
    try:
        generator = ProfessionalChartGenerator(trades, initial_capital, daily_data, xtdata_module, warmup_start_date, daily_capital_usage)
        generator.generate_all_charts(save_path)
    except Exception as e:
        print(f"专业图表生成失败: {str(e)}")
        import traceback
        traceback.print_exc()
        raise


# ==================================================================================
# K线图生成功能（向后兼容函数）
# ==================================================================================

def generate_kline_charts(trades: List[Dict], daily_data: Dict[str, pd.DataFrame], 
                         save_path_prefix: str, xtdata_module=None):
    """
    生成个股K线图的便捷函数（向后兼容）
    
    注意：此函数已整合到 ProfessionalChartGenerator 类中
    建议使用 generate_professional_charts() 统一接口
    
    Args:
        trades: 交易记录列表
        daily_data: 日线数据字典 {stock_code: DataFrame}
        save_path_prefix: 保存路径前缀
        xtdata_module: xtdata模块（用于获取股票名称）
    """
    try:
        # 使用统一的图表生成器，仅生成K线图
        generator = ProfessionalChartGenerator(
            trades=trades, 
            initial_capital=100000,  # K线图不需要初始资金
            daily_data=daily_data, 
            xtdata_module=xtdata_module,
            warmup_start_date=None,
            daily_capital_usage=[]  # K线图不需要资金统计，传空列表
        )
        generator._generate_kline_charts(save_path_prefix)
    except Exception as e:
        logger.error(f"K线图生成失败: {str(e)}")
        import traceback
        traceback.print_exc()


# K线图配置常量
class KlineConfig:
    """K线图配置"""
    # 图表尺寸（合理尺寸）
    FIGURE_SIZE = (24, 16)
    DPI = 300
    
    # 图表边距配置（减小边距，让K线图区域更大）
    MARGINS = {
        'left': 0.06,    # 左边距
        'right': 0.98,   # 右边距
        'top': 0.96,     # 上边距
        'bottom': 0.08,  # 下边距
        'hspace': 0.02   # 子图间距
    }
    
    # 标记位置偏移（相对于Y轴范围的比例）
    MARKER_LINE_GAP = 0.032   # 标记线与K线的间隙（原0.008，现为原来的4倍）
    MARKER_LINE_LENGTH = 0.252  # 标记线延伸长度（原0.063，现为原来的4倍）
    
    # 颜色配置（中国股市习惯：红涨绿跌）
    COLOR_UP = 'red'
    COLOR_DOWN = 'green'
    COLOR_MA5 = 'orange'
    COLOR_MA10 = 'purple'
    COLOR_MA20 = 'blue'
    
    # 字体配置
    FONTS = ['SimHei', 'Microsoft YaHei', 'DejaVu Sans']


def _prepare_stock_data(stock: str, daily_data: Dict[str, pd.DataFrame], warmup_start_date=None) -> Optional[pd.DataFrame]:
    """
    准备并验证股票数据
    
    Args:
        stock: 股票代码
        daily_data: 日线数据字典
        warmup_start_date: 预测开始日期（可选），如果提供，将从此日期开始显示
    
    Returns:
        处理好的DataFrame，如果数据无效则返回None
    """
    if stock not in daily_data:
        logger.warning(f"无{stock}的日线数据")
        return None
    
    stock_df = daily_data[stock].copy()
    
    # 验证必要字段
    required_columns = ['open', 'high', 'low', 'close', 'volume']
    if not all(col in stock_df.columns for col in required_columns):
        logger.error(f"{stock}缺少必要字段")
        return None
    
    # 重命名列以符合mplfinance要求
    stock_df = stock_df.rename(columns={
        'open': 'Open', 'high': 'High', 'low': 'Low',
        'close': 'Close', 'volume': 'Volume'
    })
    
    # 确保索引是DatetimeIndex
    if not isinstance(stock_df.index, pd.DatetimeIndex):
        try:
            stock_df.index = pd.to_datetime(stock_df.index)
        except Exception as e:
            logger.error(f"转换{stock}日期索引失败: {str(e)}")
            return None
    
    # 根据预测开始日期过滤数据（只显示预测期，不显示预热期）
    if warmup_start_date is not None:
        try:
            from datetime import datetime
            if isinstance(warmup_start_date, str):
                warmup_start_date = pd.to_datetime(warmup_start_date)
            elif isinstance(warmup_start_date, datetime):
                warmup_start_date = pd.Timestamp(warmup_start_date)
            
            # 过滤：只显示预测开始日期之后的数据
            stock_df = stock_df[stock_df.index >= warmup_start_date]
            
            if len(stock_df) > 0:
                first_date = stock_df.index[0].strftime('%Y-%m-%d')
                last_date = stock_df.index[-1].strftime('%Y-%m-%d')
                logger.info(f"K线图数据范围: {first_date} 至 {last_date}")
                logger.info(f"  ⚠️  K线图只显示有交易数据的日期，如果最后日期不是回测结束日期，说明之后无交易")
            else:
                logger.warning(f"过滤后无K线数据（预测开始日期: {warmup_start_date.strftime('%Y-%m-%d')}）")
        except Exception as e:
            logger.warning(f"过滤K线数据时出错: {str(e)}，将显示全部数据")
    
    return stock_df


def _extract_trade_points(stock_trades: List[Dict]) -> Tuple[List[Dict], List[Dict], List[Dict]]:
    """
    从交易记录中提取买卖点和交易结果
    
    Returns:
        (buy_points, sell_points, trade_results)
    """
    buy_points, sell_points, trade_results = [], [], []
    
    for trade in stock_trades:
        # 解析时间
        time_field = trade.get('time', trade.get('date', ''))
        trade_time = pd.to_datetime(time_field if isinstance(time_field, str) else str(time_field))
        trade_date = trade_time.date()
        
        # 获取交易方向
        direction = trade.get('direction', trade.get('action', ''))
        
        if direction == 'buy':
            buy_points.append({
                'date': trade_date,
                'price': trade['price'],
                'reason': trade.get('reason', ''),
                'shares': trade.get('shares', 100)
            })
        elif direction == 'sell':
            is_forced = trade.get('is_forced_close', False)
            sell_point = {
                'date': trade_date,
                'price': trade['price'],
                'reason': trade.get('reason', ''),
                'profit': trade.get('profit', 0),
                'profit_rate': trade.get('profit_rate', 0),
                'hold_days': trade.get('hold_days', 0),
                'is_forced_close': is_forced
            }
            sell_points.append(sell_point)
            
            # 记录交易结果
            trade_results.append({
                'sell_date': trade_date,
                'profit': sell_point['profit'],
                'profit_rate': sell_point['profit_rate'],
                'hold_days': sell_point['hold_days'],
                'buy_price': trade.get('buy_price', 0),
                'sell_price': trade['price'],
                'is_forced_close': is_forced
            })
    
    return buy_points, sell_points, trade_results


def _get_stock_name(stock: str, xtdata_module) -> str:
    """获取股票名称"""
    if not xtdata_module:
        return ''
    try:
        detail = xtdata_module.get_instrument_detail(stock)
        return detail.get('InstrumentName', '') if isinstance(detail, dict) else ''
    except:
        return ''
    

def _create_kline_style():
    """创建K线图样式配置"""
    mc = mpf.make_marketcolors(
        up=KlineConfig.COLOR_UP,
        down=KlineConfig.COLOR_DOWN,
        edge='inherit',
        wick={'up': KlineConfig.COLOR_UP, 'down': KlineConfig.COLOR_DOWN},
        volume='inherit'
    )
    
    return mpf.make_mpf_style(
        marketcolors=mc,
        gridstyle='-',
        gridcolor='lightgray',
        facecolor='white',
        rc={
            'font.family': KlineConfig.FONTS,
            'axes.titlesize': 14,
            'axes.titleweight': 'bold',
            'axes.titlecolor': 'black'
        },
        mavcolors=[KlineConfig.COLOR_MA5, KlineConfig.COLOR_MA10, KlineConfig.COLOR_MA20]
    )


def _generate_save_path(save_path_prefix: str, stock: str, stock_name: str) -> Optional[str]:
    """生成保存路径"""
    if not save_path_prefix:
        return None
    
    safe_stock_code = stock.replace('.', '_')
    
    if stock_name:
        # 清理股票名称中的特殊字符
        safe_stock_name = "".join(
            c for c in stock_name if c.isalnum() or c in (' ', '-', '_')
        ).rstrip().replace(' ', '_')
        return f"{save_path_prefix}_{safe_stock_name}_{safe_stock_code}_kline.png"
    else:
        return f"{save_path_prefix}_{safe_stock_code}_kline.png"


def _add_trade_marker(ax, stock_df: pd.DataFrame, point: Dict, is_buy: bool, is_forced_close: bool = False):
    """
    在K线图上添加买卖点标记
    
    Args:
        ax: matplotlib轴对象
        stock_df: 股票数据DataFrame
        point: 交易点信息
        is_buy: True为买入点，False为卖出点
        is_forced_close: 是否为强制平仓
    """
    trade_date = pd.to_datetime(point['date'])
    if trade_date not in stock_df.index:
        return
    
    # 获取该日期在图表上的位置
    date_idx = stock_df.index.get_loc(trade_date)
    
    # 确定颜色、标签和价格
    if is_buy:
        price = stock_df.loc[trade_date, 'Low']
        color = KlineConfig.COLOR_UP
        label = 'B'
        direction = -1  # 向下延伸
    else:
        price = stock_df.loc[trade_date, 'High']
        # 强制平仓使用黄色背景和感叹号
        if is_forced_close:
            color = 'orange'
            label = '!'
        else:
            color = KlineConfig.COLOR_DOWN
            label = 'S'
        direction = 1  # 向上延伸
    
    # 计算Y轴范围
    y_min, y_max = ax.get_ylim()
    y_range = y_max - y_min
    
    # 延伸线起点和终点
    line_start = price + direction * y_range * KlineConfig.MARKER_LINE_GAP
    line_end = price + direction * y_range * KlineConfig.MARKER_LINE_LENGTH
    
    # 绘制延伸线（提高zorder，确保在K线之上）
    ax.plot([date_idx, date_idx], [line_start, line_end],
           color=color, linewidth=2.5, linestyle='-', alpha=0.9, zorder=100)
    
    # 添加标签（进一步提高zorder，确保标签在最上层）
    dark_color = f'dark{color}' if color in ['red', 'green'] else color
    ax.text(date_idx, line_end, label,
           fontsize=13, fontweight='bold', color='white',
           ha='center', va='center', zorder=101,
           bbox=dict(boxstyle='round,pad=0.4', facecolor=color,
                    edgecolor=dark_color, linewidth=2, alpha=0.95))


def _add_multicolor_title(ax, stock: str, stock_name: str):
    """添加多颜色标题"""
    title_y = 1.02
    
    # 构建基础标题
    base_title = f"{stock_name} ({stock})" if stock_name else f"({stock})"
    
    # 标题各部分配置
    title_parts = [
        {'text': base_title, 'x': 0.02, 'color': 'black', 'weight': 'bold', 'size': 16},
        {'text': '| 均线:', 'x': 0.16, 'color': 'black', 'weight': 'normal', 'size': 14},
        {'text': 'MA5', 'x': 0.20, 'color': KlineConfig.COLOR_MA5, 'weight': 'bold', 'size': 14},
        {'text': 'MA10', 'x': 0.225, 'color': KlineConfig.COLOR_MA10, 'weight': 'bold', 'size': 14},
        {'text': 'MA20', 'x': 0.255, 'color': KlineConfig.COLOR_MA20, 'weight': 'bold', 'size': 14},
        {'text': '| 交易:', 'x': 0.285, 'color': 'black', 'weight': 'normal', 'size': 14},
        {'text': '[B]买入', 'x': 0.32, 'color': KlineConfig.COLOR_UP, 'weight': 'bold', 'size': 14},
        {'text': '[S]卖出', 'x': 0.365, 'color': KlineConfig.COLOR_DOWN, 'weight': 'bold', 'size': 14},
        {'text': '[!]强平', 'x': 0.41, 'color': 'orange', 'weight': 'bold', 'size': 14},
    ]
    
    # 添加每个部分
    for part in title_parts:
        try:
            ax.text(part['x'], title_y, part['text'],
                   transform=ax.transAxes,
                        fontsize=part['size'],
                        fontweight=part['weight'],
                        color=part['color'],
                        verticalalignment='bottom',
                   fontfamily=KlineConfig.FONTS)
        except Exception:
            # 如果字体出现问题，使用默认字体
            ax.text(part['x'], title_y, part['text'],
                   transform=ax.transAxes,
                        fontsize=part['size'],
                        fontweight=part['weight'],
                        color=part['color'],
                        verticalalignment='bottom')


def _create_trade_info_text(trade_results, buy_points, sell_points):
    """创建交易信息文本（去除emoji避免字体警告）"""
    lines = []
    
    # 过滤掉强制平仓的交易
    normal_trades = [r for r in trade_results if not r.get('is_forced_close', False)]
    
    lines.append("交易统计")
    lines.append(f"交易次数: {len(normal_trades)}笔")
    
    if normal_trades:
        total_profit = sum([r['profit'] for r in normal_trades])
        avg_profit_rate = sum([r['profit_rate'] for r in normal_trades]) / len(normal_trades)
        avg_hold_days = sum([r['hold_days'] for r in normal_trades]) / len(normal_trades)
        
        lines.append(f"总盈亏: {total_profit:+.2f}元")
        lines.append(f"平均收益率: {avg_profit_rate:+.2f}%")
        lines.append(f"平均持有: {avg_hold_days:.1f}天")
        
        # 显示每笔交易（只显示正常交易）
        lines.append("\n交易记录:")
        for i, result in enumerate(normal_trades, 1):
            profit_symbol = "+" if result['profit'] >= 0 else ""
            lines.append(f"{i}. {profit_symbol}{result['profit']:.0f}元 "
                         f"({result['profit_rate']:+.1f}%) "
                         f"{result['hold_days']}天")
    
    return "\n".join(lines)
